#ifndef __PROTOCAL__
#define __PROTOCAL__

#include <sys/types.h>
#include <sys/stat.h>
#include <iostream>
#include <sys/wait.h>
#include <sys/sendfile.h>
#include <unistd.h>
#include <string>
#include <cstring>
#include <unordered_map>
#include <vector>
#include <algorithm>
#include <mutex>
#include <sys/types.h>
#include <fcntl.h>
#include "TcpSocket.hpp"
#include <sstream>
#include "Util.hpp"
#include <utility>

#include "MyEpoll.hpp"

int STOP = 0; // 1：出现错误； 0: success;  -1: Send未发送完毕,socket暂时不关闭，调用新线程完成发送剩余信息

#define OK 200
#define NO_FIND 404

#define WEBROOT "/webroot"
#define HOME_PAGE "/home_page.html"
#define LINE_END "\r\n"
#define HTML404 "/404.html"

// 匹配状态码解释
static std::string CodeCommit(int code)
{
    std::string tmp;
    switch (code)
    {
    case 404:
        tmp = "资源未找到";
        break;

    case 200:
        tmp = "OK";
        break;

    default:
        break;
    }
    return tmp;
}

// 响应协议
class HttpResponse
{
public:
    std::string Respone_Line;             // 响应行
    std::vector<std::string> hander_list; // 响应报头集合
    std::string Respone_Blank;            // 响应空行
    std::string Respone_body;             // 响应正文

    int status; // 响应状态码, 1: 已响应；0：未响应

    int code = OK;      // 响应 描述符
    unsigned int size;  // 资源文件的字节大小
    std::string suffix; // 资源后缀名

public:
    HttpResponse()
        : size(0)
    {
    }

    ~HttpResponse()
    {
    }
};

// 请求协议
class HttpWork
{
private:
    // 报文四部分
    std::string line;                        // 请求首行
    std::vector<std::string> Request_header; // 请求报头 ————暂时设置string,具体请求参数暂定
    std::string Blank;                       // 请求选项
    std::string request_body;                // 请求主体
    // 首行
    std::string method; // 请求方式
    std::string url;    // 请求网址
    std::string verson; // 版本号
    // 解析报头
    std::unordered_map<std::string, std::string> header_kv; // 请求报头，参数设置
    ssize_t content_length;                                 // 正文长度
    // 对网址进行解析
    std::string address;   // 请求路径
    std::string parameter; // 请求参数

    bool cgi; // CGI处理标识符
    size_t _socket;

    ConnectData *_connect;

private:
    HttpResponse httpresponse; // 响应类

public:
    HttpWork(size_t socket, ConnectData *con)
        : content_length(0), cgi(false), _socket(socket), _connect(con)
    {
        // 取掉行
        // 取掉首部
        if (RecvHttpRequestLine() == -1 ||
            (RecvHttpRequestHander() == -1))
        {
            WARN("Rec fail");
            STOP = 1;
            // httpresponse.code = 404;
        }
    }

    // 分析请求报文
    int PaseHttpRequest_main()
    {
        PaseHRline();
        PaseHRHeader();
        PaseURL();
    }

    // 响应报文构建主程序
    int BuildHttpRespose_main()
    {
        // 1.检测请求中信息，并检测期中错误，选项
        check_elements();

        // 3.对请求资源进行分析
        check_resources();

        // 静态请求 & CGI响应分流 & 404响应分流
        return request_shunt();
    }

    ~HttpWork()
    {
    }

    // 接收函数
private:
    // 接收首行
    int RecvHttpRequestLine()
    {
        if (Util::Readline(_socket, line) == -1)
            return -1;
        return 0;
    }
    // 接收报文头部
    int RecvHttpRequestHander()
    {
        int set = 0;
        while (1)
        {
            std::string head_str;
            if (Util::Readline(_socket, head_str) == -1)
                return -1;

            if (head_str.size() == 0)
                break;
            // head_str.resize(head_str.size() - 1);
            Request_header.push_back(head_str);
        }
        // INFO("Recv Hander success");
        return 0;
    }

    // 接收正文
    int RecvHttpRequestBody()
    {
        int length = content_length;
        while (length)
        {
            char x = 'g';
            int bx = recv(_socket, &x, 1, 0);
            if (bx == -1)
                WARN("接收正文错误：%s", strerror(errno));

            request_body.push_back(x);
            length--;
        }
        // INFO("recv body success : %s", request_body.c_str());
        return 0;
    }
    

    // 解析报文
private:
    // 解析URL参数，有参数返回0，否则-1
    int PaseURL()
    {
        // URL中路径与参数的分割符为"?"
        return Util::Stringcut(url, address, parameter, "?");
    }
    
    // 解析首行
    int PaseHRline()
    {
        std::stringstream ss(line);
        ss >> method >> url >> verson;
        std::transform(method.begin(), method.end(), method.begin(), ::towupper);
        INFO("method:%s", method.c_str());
        INFO("url:%s", url.c_str());
        
        int cur = 0;
        if ((cur = url.rfind("#!")) != std::string::npos)
        {
            url = url.substr(0, cur);
        }
        // INFO("verson:%s", verson.c_str());
        return 0;
    }
    // 解析头部
    int PaseHRHeader()
    {
        for (auto &n : Request_header)
        {
            std::string key;
            std::string value;
            Util::Stringcut(n, key, value);
            header_kv.insert({key, value});
            // Logmessage(INFO, "%s: %s", key.c_str(), value.c_str());
        }

        if (method == "POST")
            ;
        content_length = atoi(header_kv["Content-Length"].c_str());
    }
    
    bool IsGet()
    {
        if (method == "GET")
            return 0;
        return -1;
    }

private:
    // 检查元素get,post
    void check_elements()
    {
        if (method != "GET" && method != "POST")
        {
            httpresponse.code = NO_FIND;
        }

        if (address == "/favicon.ico")
        {
            INFO("地址未标明,重定向");
            address = "/";
        }else if (address == "/index.html")
        {
            WARN("新index.html 重定向");
            address = "/cgi_sql/blog_r_list_sql.cgi";
            parameter = "seat=1"; //cgi请求，随便设置一个参数即可
        }
    }

    // 检查资源是否存在
    void check_resources()
    {
        std::string tmp = WEBROOT;
        tmp += address;
        address = tmp;
        INFO("URL 路径：%s", address.c_str());
        INFO("URL 参数：%s", parameter.c_str());
        if (address[address.size() - 1] == '/')
        {

            address += "index.html"; // 暂时替代
        }
        else
        {
            // 判断该资源是否存在
            struct stat file_stat;
            std::string tmp = ".";
            int i;
            if ((i = stat((tmp + address).c_str(), &file_stat)) == 0)
            {
                // 是目录？若是则重定向一下
                if (S_ISDIR(file_stat.st_mode))
                {
                    address += HOME_PAGE;
                    stat((tmp + address).c_str(), &file_stat);
                }
                httpresponse.size = file_stat.st_size;
                // 检测是否是可执行程序
                if (address.substr(address.rfind(".") + 1) == "cgi")
                {
                    FAIL("不是CGI就触发了 %s", address.c_str());
                    cgi = true;
                }
                else
                {
                    // std::cout << "其他类型文件处理正在开发中" << std::endl;
                }
            }
            else
            {
                WARN("file not find:%s, errno: %s", address.c_str(), strerror(errno));
                httpresponse.code = NO_FIND;
            }
        }

        if (address.find("blog_del_sql.cgi") != std::string::npos)
        {
            httpresponse.code = 303;
            httpresponse.hander_list.push_back("Location: /index.html");
        }
    }

    // 静态请求 & CGI响应分流
    int request_shunt()
    {
        if (!cgi && httpresponse.code == OK)
        {
            // 响应行
            BuildResponseLine();
            Build_H_Send_H_Body();
            return 0;
        }
        // CGI响应
        else if (cgi && httpresponse.code != NO_FIND)
        {
            if (method == "POST")
                RecvHttpRequestBody();
            ProcessCGI(); // CGI处理
            BuildResponseLine();
            Build_H();
            Send_L_H();
            return SendBody(-1); // post，正文从body中获取，所以就不用，填写打开的html文件句柄
        }
        else // code 错误，构建404报文
        {
            WARN("code 404");
            // Error_response(HTML404);
            return -1;
        }
    }

    // 构造响应首行
    int BuildResponseLine()
    {
        httpresponse.Respone_Line = verson;
        httpresponse.Respone_Line += " ";
        httpresponse.Respone_Line += std::to_string(httpresponse.code);
        httpresponse.Respone_Line += " ";
        httpresponse.Respone_Line += CodeCommit(httpresponse.code);
        httpresponse.Respone_Line += LINE_END;
    }

    // 采用传统的阻塞式send
    int sender(int fd, std::string &room)
    {
        // 还是采用传统的发送方式
        if (send(_socket, room.c_str(), room.size(), 0) == -1)
        {
            STOP = 1;
            WARN("send1 fail");
            return -1;
        }

        return 0;
    }

    // 发送首行,头部
    void Send_L_H()
    {
        // send首行，头部
        httpresponse.Respone_Blank = LINE_END;
        std::string room;
        room += httpresponse.Respone_Line;
        for (auto &i : httpresponse.hander_list)
        {
            room += i;
            room += LINE_END;
        }
        room += httpresponse.Respone_Blank;

        if (sender(_socket, room) == -1)
        {
            STOP = 1;
            FAIL("send1 fail");
        }
    }

    // 构建头部
    void Build_H()
    {
        std::string tmp = "Conent-Length";
        tmp += SEQ;
        tmp += std::to_string(httpresponse.Respone_body.size());
        httpresponse.hander_list.push_back(tmp);
    }
    // 发送正文
    int SendBody(int pd)
    {
        // 正文缓冲区部分未被添加，则是响应文件资源，sendfile; 同理，被添加则是CGI式请求
        if (httpresponse.Respone_body.size() != 0)
        {
            if (sender(_socket, httpresponse.Respone_body) == -1)
                WARN("send1 fail");
        }
        else if (pd != -1)
        {
            if (sendfile(_socket, pd, nullptr, httpresponse.size) == -1)
                WARN("send2 fail");
            close(pd);
        }
        INFO("已响应");
        return 0;
    }

    // 针对主要是主要是"GET"，资源响应式请求
    int Build_H_Send_H_Body()
    {
        // 获取资源后缀
        httpresponse.suffix = address.substr(address.rfind(".") + 1);
        std::string ptr = ".";
        ptr += address;
        int pd = -1;

        pd = open(ptr.c_str(), O_RDONLY);
        if (pd == -1)
        {
            FAIL("resource file open fail :", ptr.c_str());
            return -1;
        }

        // 打开即将发送的文件句柄，并记录资源大小,也可以使用fstat
        struct stat file;
        stat(ptr.c_str(), &file);
        httpresponse.size = file.st_size;

        std::string tmp = "Content-Length";
        tmp += SEQ;
        tmp += std::to_string(httpresponse.size); // 这里设置了长度，能确定这里一定是纯正文么？
        httpresponse.hander_list.push_back(tmp);

        // 覆盖重用,文件类型添加
        std::string mime = Content_type_transform(httpresponse.suffix);
        std::string suffix_tmp = "Content-Type";
        suffix_tmp += SEQ;
        suffix_tmp += mime;
        suffix_tmp += ";charset=UTF-8";
        // 还有这里，你需要设置的头部应该是 Content-Type: application/jpg 而不是 jpg:not foundxxxxxxx
        httpresponse.hander_list.push_back(suffix_tmp);

        Send_L_H();
        return SendBody(pd);
    }

    // CGI模式
    int ProcessCGI()
    {
        std::string tmp_iurlb = ".";
        tmp_iurlb += address;
        int output[2]; // 线程发数据
        int inget[2];  // 线程接收结果
        pipe(output);
        pipe(inget);
        pid_t pd = fork();
        // 线程为主视角
        if (pd == 0)
        {
            // 子
            close(output[1]);
            close(inget[0]);
            // 1.接收数据,准备程序替换

            std::string room = "METHOD="; // 请求类型环境变量
            room += method;
            putenv((char *)room.c_str());

            std::string tmp;         // get参数 环境变量
            std::string room_length; // post正文的长度环境变量

            if (method == "POST")
            {
                room_length = "CONTENT-LENHTH=";
                room_length += std::to_string(content_length);
                putenv((char *)room_length.c_str());
            }
            else if (method == "GET" && parameter.size() != 0)
            {
                tmp = "PARAMENTER=";
                tmp += parameter;
                putenv((char *)tmp.c_str());
            }else
            {
                std::cerr << "url中不存在参数" << std::endl;
                exit(1);
            }

            // 约定：子进程只需从标准输入输出进行获取数据
            dup2(output[0], 0);
            dup2(inget[1], 1);
            execl(("." + address).c_str(), nullptr); // 疑问：既然通过环境变量来传参数，那buff到时候传过去的就是
            std::cerr << "\033[31m GET EXECL FAIL address:" << address
                      << strerror(errno) << "\033[0m" << std::endl;
            return -1;
        }
        else if (pd > 0)
        {
            // 父
            close(output[0]);
            close(inget[1]);

            if (method == "POST")
            {
                const char *str = request_body.c_str();
                int total = 0;
                int size = 0;
                while ((total <= content_length) &&
                       (size = write(output[1], str + total, request_body.size() - total) > 0))
                {
                    total += size;
                }
            }

            // 接收CGI 返回值
            std::string tmp;
            while (1)
            {
                char x;
                int set = read(inget[0], &x, 1);
                if (set < 0)
                {
                    FAIL("read CGI 管道出错: %s", strerror(errno));
                    exit(1);
                }
                if (x == '\n')
                    break;
                tmp += x;
            }
            // 从CGI返回值中，标准是获取正文类型，以及正文数据
            httpresponse.Respone_body = tmp.substr(tmp.find("\r\r") + 2);
            httpresponse.hander_list.push_back(tmp.substr(0, tmp.find("\r\r")));

            INFO("等待CGI结束");
            int status = 0;
            waitpid(pd, &status, 0); // 线程阻塞试等待
            if (WIFEXITED(status) == 0)
                WARN("CGI exit with error code");

            close(output[1]);
            close(inget[0]);

            return 0;
        }
        else
        {
            FAIL("fork fail");
            return -1;
        }
    }

    // 404响应, 方法：1.修改资源路径，及部分信息，就当访问404 2.通过重新构建错误的响应行，头部，正文，发送
    int Error_response(const std::string &error_address)
    {
        std::string change_url = WEBROOT;
        // change_url += error_address; 由于404未创建,因此未找到则返回主页
        change_url += "/index.html";

        address = change_url;
        httpresponse.suffix = address.substr(address.rfind(".") + 1);
        BuildResponseLine();
        Build_H_Send_H_Body();
        return 0;
    }
};

#endif
